#include <map>
#include <algorithm>
#include <iomanip>

#include "tabu.h"

Tabu::Tabu(list<Rectangulo> lista_in, Contenedor C_) : Heuristica(lista_in) {
	//cout << "+ TABU::Constructor(listado, contenedor)" << endl;
	C = C_;
	//cout << "- TABU::Constructor(listado, contenedor)" << endl;
}
         
Tabu::~Tabu() {
	//cout << "+ TABU::Destructor" << endl;
	//cout << "- TABU::Destructor" << endl;
}

list<Rectangulo> Tabu::generaInicial() {
	//cout << "+ TABU::generaInicial" << endl;

	// Generamos aleatoriamente n soluciones.
	vector <Solucion> initSolucion = generaConjuntoSoluciones(N_INIT_SOL);

	// Calculamos la altura que genera cada solución
	for (int i = 0; i < N_INIT_SOL; i++) {
		initSolucion[i].setValor(C.calcularAltura(initSolucion[i].getLista()));
	}

	// Ordenamos las soluciones según su valor y seleccionamos la mejor.
	sort(initSolucion.begin(), initSolucion.end());

	int size = initSolucion[0].getLista().size();
	
	tabuTable.assign(size, vector <vector <int> >(size, vector <int>(4, 0)));

	//cout << "- TABU::generaInicial" << endl;
	return (initSolucion[0].getLista());
}

list<Rectangulo> Tabu::mejoraSolucion() {
	//cout << "+ TABU::mejoraSolucion()" << endl; 

	// Calculamos los costos relativos
	vector <Rectangulo> temp(solucion.begin(), solucion.end());
	multimap <int , pair <pair <int, int>, pair <bool, bool> > > costos = calcularCostos(temp);

	multimap <int , pair <pair <int, int>, pair <bool, bool> > >::iterator it;
	for (it = costos.begin(); it != costos.end(); it++) {
		int i = (it -> second).first.first;
		int j = (it -> second).first.second;

		bool roti = (it -> second).second.first;
		bool rotj = (it -> second).second.second;

		int idMin = temp[i].getid() < temp[j].getid()? temp[i].getid(): temp[j].getid();
		int idMax = temp[i].getid() > temp[j].getid()? temp[i].getid(): temp[j].getid();

		pair <pair<int, int>, pair<bool, bool> > tlEntry(make_pair(idMin, idMax), make_pair(roti, rotj));

		if (find(tabuList.begin(), tabuList.end(), tlEntry) == tabuList.end() || ((it -> first) < 0)) {

			// Mostrar
			//----------------------------------------------------//
			/* //No se recomienda usar para mas de 10 elementos
			cout << "    ";
			for (int a = 0; a < tabuTable.size(); a++) {
				cout << setw(2) << a << "           ";
			}
			cout << endl;

			multimap <int , pair <pair <int, int>, pair <bool, bool> > >::iterator it2 = costos.begin();
			list <Rectangulo>::iterator it3 = solucion.begin();

			for (int a = 0; a < tabuTable.size(); a++) {
				cout << setw(3) << it3 -> getid();

				cout << " ";
				for (int b = 0; b < tabuTable.size(); b++) {
					cout << "(" << tabuTable[a][b][0];
					for (int w = 1; w < 4; w++) {
						cout << ", " << tabuTable[a][b][w];
					}
					cout << ") ";
				}
			
				int i = (it2 -> second).first.first;
				int j = (it2 -> second).first.second;
			
				int roti = (it2 -> second).second.first;
				int rotj = (it2 -> second).second.second;

				if (it2 != costos.end()){
					int idMin = temp[i].getid() < temp[j].getid()? temp[i].getid(): temp[j].getid();
					int idMax = temp[i].getid() > temp[j].getid()? temp[i].getid(): temp[j].getid();	

					cout << setw(2) << (it2 -> first) << ", (" << i << "[" << roti << "], ";
					cout << j << "[" << rotj << "]" << ")" << endl;
					
					it2++;
				}
				else {
					cout << endl;
				}

				it3++;
			}

			int test;
			cin >> test;
			*/
			//----------------------------------------------------//

			updateTabuList();

			if (temp[i].isRotado() != roti) {
				temp[i].rotar();
			}

			if (temp[j].isRotado() != rotj) {
				temp[j].rotar();
			}

			int rotacion;
			if (roti == false) {
				if (rotj == false) {
					rotacion = 0;
				}
				else {
					rotacion = 1;
				}
			}
			if (roti == true) {
				if (rotj == false) {
					rotacion = 2;
				}
				else {
					rotacion = 3;
				}
			}

			swap(temp[i], temp[j]);

			tabuTable[idMin][idMax][rotacion] = TABU_TIME;

			tabuList.push_back(tlEntry);

			solucion.clear();
			solucion.resize(temp.size());
			
			copy(temp.begin(), temp.end(), solucion.begin());
	
			//cout << "- TABU::mejoraSolucion()" << endl;			
			return solucion;
		}
	}
	//cout << "- TABU::mejoraSolucion" << endl;
}

multimap <int , pair <pair <int, int>, pair <bool, bool> > > Tabu::calcularCostos(vector <Rectangulo>& temp) {
	//cout << "+ TABU::calcularCostos(temp)" << endl;
	multimap <int , pair <pair <int, int>, pair <bool, bool> > > costos;

	int minActual = C.calcularAltura(solucion);

	for (int i = 0; i != temp.size(); i++) {
		for (int j = i + 1; j < temp.size(); j++) {
			pair <int, int> pos(i, j);
			pair <bool, bool> rot(temp[i].isRotado(), temp[j].isRotado());

			pair <pair <int, int>, pair <bool, bool> > data(pos, rot);
			swap(temp[i], temp[j]);
			
			// Valor con la rotacion actual
			int valor = C.calcularAltura(list <Rectangulo>(temp.begin(), temp.end())) - minActual;

			pair <int, pair <pair <int, int>, pair <bool, bool> > > entry(valor, data);
			costos.insert(entry);

			// Valor con i girado (actualmente es j)
			temp[j].rotar();

			rot.first = !rot.first;
			data.second = rot;
			entry.second = data;
			valor = C.calcularAltura(list <Rectangulo>(temp.begin(), temp.end())) - minActual;
			entry.first = valor;
			costos.insert(entry);

			// Valor con ambos girados
			temp[i].rotar();

			rot.second = !rot.second;
			data.second = rot;
			entry.second = data;
			valor = C.calcularAltura(list <Rectangulo>(temp.begin(), temp.end())) - minActual;
			entry.first = valor;
			costos.insert(entry);

			// Valor j girado (actualmente es i)
			temp[j].rotar();

			rot.first = !rot.first;
			data.second = rot;
			entry.second = data;
			valor = C.calcularAltura(list <Rectangulo>(temp.begin(), temp.end())) - minActual;
			entry.first = valor;
			costos.insert(entry);

			// Restauramos el elemento
			temp[i].rotar();
			swap(temp[i], temp[j]);
		}

		if (costos.size() > TABU_TIME + 1) {
			multimap <int , pair <pair <int, int>, pair <bool, bool> > >::iterator xt = costos.begin();
			xt++;xt++;xt++;xt++; // costos.begin() +4
			costos.erase(xt, costos.end());
		}
	}
	
	return costos;
	//cout << "- TABU::calcularCostos(temp)" << endl;
}

void Tabu::updateTabuList() {
	pair <pair<int, int>, pair<bool, bool> > borrar;
	bool del = false;
	
	for (int x = 0; x < tabuList.size(); x++) {
		int i = tabuList[x].first.first;
		int j = tabuList[x].first.second;

		bool roti = tabuList[x].second.first;
		bool rotj = tabuList[x].second.second;

		int rotacion;

		if (roti == false) {
			if (rotj == false) {
				rotacion = 0;
			}
			else {
				rotacion = 1;
			}
		}
		if (roti == true) {
			if (rotj == false) {
				rotacion = 2;
			}
			else {
				rotacion = 3;
			}
		}

		tabuTable[i][j][rotacion] = tabuTable[i][j][rotacion] - 1;

		if (tabuTable[i][j][rotacion] == 0) {
			borrar = make_pair(make_pair(i, j), make_pair(roti, rotj));
			del = true;
		}
	}
	
	if(del == true) {
		tabuList.erase(tabuList.erase(find(tabuList.begin(), tabuList.end(), borrar)));
	}
}
